// the information used to read CSV BTP files was based from the J3D Animation Editor program
// https://github.com/tarsa129/j3d-animation-editor
// description about the CSV BTP file format can also be found on the page mentioned above

// header that has functions to read CSV BTP files data

#ifndef CSV_BTP_HEADER
#define CSV_BTP_HEADER

// THIS HEADER NEEDS:
#include "JSYSTEM.h" 
#include "BMD-BDL.h" 
#include "BIN-BTP.h" 
#include "CSV.h"

// CSV BTP CONSTANTS

const void * CSV_BTP_CONST_STRS[5] = {".btp", "Material", "Name", "Duration", "Frame"};
byte_t CSV_BTP_ALWAYS_ZERO = 0;

// functions

FILE_T CHECK_CSV_BTP(char_t * file_path);
bool CHECK_CSV_BTP_WITH_BMD_BDL(byte_t* anim_file_path, byte_t* model_file_path);
void BUILD_CSV_BTP_STRUCT(struct CSV_BTP * ANIM, byte_t* DATA);
void PRINT_CSV_BTP_STRUCT(byte_t* file_path);
void WRITE_BIN_BTP(byte_t* anim_file_path, byte_t* model_file_path);


////////////
// FUNCTIONS
////////////

///////////////////////////
// CHECK_CSV_BTP() function
// function to check a CSV BTP file
FILE_T CHECK_CSV_BTP(char_t * file_path)
{ 
  // create CSV struct
  CSV_ST CSV = BUILD_CSV_STRUCT(file_path);
  if (CSV == NULL)
    return NO_TYPE;
  
  // check the header
  
  
  // free memory used  
  CLOSE_CSV(CSV);
  
  // BUILD_CSV_BTP_STRUCT() will basically
  // check the file for errors, no need to
  // do a whole new function for it.

  printf("Input CSV BTP file is OK (by itself). FILE SIZE: %u.\n", get_file_size(csv_btp_path));
  
  return CSV_BTP_TYPE;
}


////////////////////////////////
// CHECK_CSV_BTP_WITH_BDL() function
// function to check if a binary BTP file is related to a BDL file
//
// anim_file_path  (byte_t*) --> BIN BTP file path
// model_file_path  (byte_t*) --> BDL file path
bool CHECK_CSV_BTP_WITH_BMD_BDL(byte_t* anim_file_path, byte_t* model_file_path)
{
  // compare:
  // - texture number count     (FATAL)
  // - material count           (FATAL)
  // - material names           (FATAL)
  // - material string weights  (CAN BE OMITTED BY SOME GAMES)
  // - material position table  (CAN BE OMITTED BY SOME GAMES)
  
  // error checking variable
  // will only use in this function:
  // BAD_MAT_POS_TABLE
  // BAD_STR_WEIGHT
  // NOT_RELATED_TO_BMD_BDL
  
  enum BTP_ERROR_CODES CSV_BTP_CODE = NO_ERROR;
  
  // load both files in memory
  byte_t* anim_file = load_file(anim_file_path);
  byte_t* model_file = load_file(model_file_path);
  
  // create structs and fill them
  struct CSV_BTP CSV_BTP;
  BUILD_CSV_BTP_STRUCT(&CSV_BTP, anim_file);
  struct BIN_BMD_BDL MODEL;
  BUILD_BIN_BMD_BDL_STRUCT(&MODEL, model_file);
  
  // anim_file can be freed now
  free_memory(anim_file);
  
  // get texture count on the BDL and check the texture
  // swap table on the CSV BTP for illegal tex numbers
  // they can be used but I won't allow it >:]
  u2 tex_count_bdl = GET_DATA_TYPE(2, U_INT, model_file + MODEL.TEX_COUNT, NULL, MODEL_ENDIAN);
  u2 mat_anim_length_sum_csv_btp = 0;
  for (int i = 0; i < CSV_BTP.ANIM_MAT_COUNT; i++)
    mat_anim_length_sum_csv_btp += CSV_BTP.MAT_LAST_ANIM_DATA_FRAME[i];
      
  for (int i = 0; i < mat_anim_length_sum_csv_btp; i++)
    if (CSV_BTP.MAT_TEX_SWAP_DATA[i] >= tex_count_bdl)
    {
      CSV_BTP_CODE = BTP_NOT_RELATED_TO_MODEL;
      break;
    }
  
  // check material count on the BTP
  u2 mat_count_bdl = GET_DATA_TYPE(2, U_INT, model_file + MODEL.MAT_COUNT, NULL, MODEL_ENDIAN);
  if (CSV_BTP.ANIM_MAT_COUNT > mat_count_bdl)
    CSV_BTP_CODE = BTP_NOT_RELATED_TO_MODEL;
  
  // material names
  int str_pos_csv_btp = 0;
  for (int i = 0; i < CSV_BTP.ANIM_MAT_COUNT; i++)
  {    
    // get material name position on CSV_BTP struct
    if (i != 0)
    {
      while (CSV_BTP.MAT_NAMES[str_pos_csv_btp] != 0)
        str_pos_csv_btp++;
      str_pos_csv_btp++;
    }
    
    // search for the material on the BDL file (go through all the list)
    // prepare the variable for the next loop
    enum BTP_ERROR_CODES CSV_BTP_CODE_TEMP = BTP_NOT_RELATED_TO_MODEL;
    for (int j = 0; j < mat_count_bdl; j++)
    {
      // get material str weight and string position on BDL
      u4 str_pos_bdl = MODEL.MAT_NAME_TABLE + 
                       GET_DATA_TYPE(2, U_INT, model_file + MODEL.MAT_NAME_INF + (4 * j) + 2, NULL, MODEL_ENDIAN);
      
      // material has been found
      // str comparison
      if (compare_strings(CSV_BTP.MAT_NAMES + str_pos_csv_btp, model_file + str_pos_bdl, 0))
      {
        CSV_BTP_CODE_TEMP = BTP_NO_ERROR;        
        break;
      }
    }
    
    // no material string match has been found on BDL
    if (CSV_BTP_CODE_TEMP == BTP_NOT_RELATED_TO_MODEL)
    {
      CSV_BTP_CODE = BTP_NOT_RELATED_TO_MODEL;
      break;
    }
  }
  
  // free model_file
  free_memory(model_file);
  
  // free memory used on CSV_BTP
  free_memory(CSV_BTP.KEYFRAMES);
  free_memory(CSV_BTP.MAT_NAMES);
  free_memory(CSV_BTP.MAT_LAST_ANIM_DATA_FRAME);
  free_memory(CSV_BTP.MAT_TEX_SWAP_DATA);
  
  printf("\n");
  // check which error code was stored on BTP_CODE
  switch (CSV_BTP_CODE)
  {
    // NO_ERROR means that the file is good
    case (BTP_NO_ERROR):
      printf("Input CSV BTP file is compatible with input BMD/BDL file.\n");
      return true;
    case (BTP_NOT_RELATED_TO_MODEL):
      printf("Input CSV BTP file is NOT compatible with input BMD/BDL file.\n");
      break;
    case (BTP_HEADER):
    case (BTP_CONSTANT):
    case (BTP_INCONSISTENT_DATA):
    case (BTP_BAD_STR_WEIGHT):
    case (BTP_BAD_MAT_POS_TABLE):
    case (BTP_ENDIAN_DIF_THAN_MODEL):
      printf("Impossible to get this error for a CSV BTP right now.\n");
      printf("There must be a program error somewhere. ERROR CODE: %d.\n\n", CSV_BTP_CODE);
      break;
      
  }
  
  // if the BTP file isn't related
  // to the BDL one, return false
  return false;
}


//////////////////////////////////
// BUILD_CSV_BTP_STRUCT() function
// function to store the data read from a CSV BTP in a CSV_BTP struct
// 
// CSV_BTP   (struct BIN_BTP *) --> pointer table for the CSV BTP file
// file_path (byte_t*)           --> CSV BTP file path string
void BUILD_CSV_BTP_STRUCT(struct CSV_BTP * ANIM, byte_t* DATA)
{  
  // CSV BTP description
  
  // loop mode, max animation length, 0, ".btp"                             (header 1st line)
  // "Material Name","Duration", "Frame" 0, ... Keyframe numbers ...        (header 2nd line)
  // Mat 1 name, Mat 1 anim length, Frame 0 value, ... Keyframe values ...  (contents)
  // ...
  
  // variable to track byte
  // position on DATA array
  u4 i = -1;
  
  // temp variables
  s8 temp = 0;
  
  // get material count  
  // count relevant newlines
  do
  {
    i = get_to_nextln_data(i, DATA);
    if (DATA[i] != 0)
      temp++;
  } while (DATA[i] != 0);
  // in a CSV BTP there should be (number of materials + 2) lines
  ANIM -> ANIM_MAT_COUNT = temp - 2;
  
  // reset counter
  i = -1;
  
  // will use the get integer function to be able to keep track of i's value
  
  // first line
  i = get_to_nextln_data(i, DATA);
  // loop mode
  temp = get_csv_integer(&i, DATA);
  if (temp < 0 || temp > 4)
    inconsistent_data(i);
  ANIM -> LOOP_MODE = temp;
  i = ignore_wsp_until(i, DATA, true);
  
  // last animation frame
  temp = get_csv_integer(&i, DATA);
  if (temp <= 0)
    inconsistent_data(i);
  ANIM -> LAST_ANIM_FRAME = temp;
  i = ignore_wsp_until(i, DATA, true);
  
  // always 0
  temp = get_csv_integer(&i, DATA);
  if (temp != CSV_BTP_ALWAYS_ZERO)
    inconsistent_data(i);
  i = ignore_wsp_until(i, DATA, true);
  
  // ".btp" string
  byte_tCSV_BTP_ID[5] = {0};
  cp_mem_bytes(DATA + i, 4, CSV_BTP_ID);
  i += 4;
  if (compare_strings(CSV_BTP_ID, CSV_BTP_CONST_STRS[0], 0) == false)
    inconsistent_data(i);
  
  // second line
  i = get_to_nextln_data(i, DATA);
  
  // always "Material"
  byte_tstr_constant_1[9] = {0};
  cp_mem_bytes(DATA + i, 8, str_constant_1);
  i += 8;
  if (compare_strings(str_constant_1, CSV_BTP_CONST_STRS[1], 0) == false)
    inconsistent_data(i);
  
  // skip whitespace (without comma)
  i = ignore_wsp_until(i, DATA, false);
  
  // always "Name"
  byte_tstr_constant_2[5] = {0};
  cp_mem_bytes(DATA + i, 4, str_constant_2);
  i += 4;
  if (compare_strings(str_constant_2, CSV_BTP_CONST_STRS[2], 0) == false)
    inconsistent_data(i);
  i = ignore_wsp_until(i, DATA, true);
  
  // always "Duration"
  byte_tstr_constant_3[9] = {0};
  cp_mem_bytes(DATA + i, 8, str_constant_3);
  i += 8;
  if (compare_strings(str_constant_3, CSV_BTP_CONST_STRS[3], 0) == false)
    inconsistent_data(i);
  i = ignore_wsp_until(i, DATA, true);
  
  // get the largest keyframe 
  // (to hold the material animation data)
  temp = i;
  u2 max_anim_frame = 0;
  for (int j = 0; /**/; j++)
  {    
    // always "Frame"
    byte_tstr_constant_4[6] = {0};
    cp_mem_bytes(DATA + i, 5, str_constant_4);
    i += 5;
    if (compare_strings(str_constant_4, CSV_BTP_CONST_STRS[4], 0) == false)
      inconsistent_data(i);
    i = ignore_wsp_until(i, DATA, false);
    
    // keyframe frame number
    // keyframe 1 is always the first frame and is ALWAYS there
    u2 temp_frame = get_csv_integer(&i, DATA);
    if ((j == 0 && temp_frame != 1) || temp < 0)
      inconsistent_data(i);
    // max keyframe number on the CSV
    else if (temp_frame > max_anim_frame)
      max_anim_frame = temp_frame;
    i = ignore_wsp_until(i, DATA, false);
        
    if (DATA[i] == '\n' || DATA[i] == '\r')
      break;
    else  
      i = ignore_wsp_until(i, DATA, true);
  }
  
  // go back to last i position
  i = temp;
  
  // get the animation keyframes
  ANIM -> KEYFRAMES = allocate_memory(max_anim_frame, 2);
  temp = 0;
  for (int j = 0; /**/; j++)
  {    
    // "Frame"
    i += 5;
    i = ignore_wsp_until(i, DATA, false);
    
    // keyframe frame number
    // keyframe 1 is always the first frame and is ALWAYS there
    temp = get_csv_integer(&i, DATA);
    (ANIM -> KEYFRAMES)[j] = temp;
    i = ignore_wsp_until(i, DATA, false);
        
    if (DATA[i] == '\n' || DATA[i] == '\r')
      break;
    else  
      i = ignore_wsp_until(i, DATA, true);
  }
  
  // get to next line (material data starts)
  i = get_to_nextln_data(i, DATA);
  temp = i;
  
  // will have to go through 2 times, one to get the material string length sum
  // and the other one to actually read all the data
  u4 str_length_sum = 0;    
  while (DATA[i] != 0)
  {
    while (DATA[i] != ' ' && DATA[i] != '\t' && DATA[i] != ',')
    {
      str_length_sum++;
      i++;
    }
    str_length_sum++;
    i = get_to_nextln_data(i, DATA);
  }
  
  // go back to position stored in temp
  i = temp;
  
  // allocate enough space for:
  // - the material name strings
  // - the last frame with animation data for a material
  // - the material texture swap data (all of the animation
  //   data no matter the last keyframe value)
  ANIM -> MAT_NAMES = allocate_memory(str_length_sum, 1);
  ANIM -> MAT_LAST_ANIM_DATA_FRAME = allocate_memory(ANIM -> ANIM_MAT_COUNT, 2);
  ANIM -> MAT_TEX_SWAP_DATA = allocate_memory((ANIM -> ANIM_MAT_COUNT) * (max_anim_frame), 2);
  // temp variables to be used to read this part
  u4 temp_str_length_sum = 0;
  u4 tex_swap_pos = 0;
  for (int j = 0; j < ANIM -> ANIM_MAT_COUNT; j++)
  {    
    // read material name
    while (DATA[i] != ' ' && DATA[i] != '\t' && DATA[i] != ',')
    {
      ANIM -> MAT_NAMES[temp_str_length_sum] = DATA[i];
      temp_str_length_sum++;  i++;
    }
    temp_str_length_sum++;
    i = ignore_wsp_until(i, DATA, true);
    
    // read material anim length
    ANIM -> MAT_LAST_ANIM_DATA_FRAME[j] = (u2) get_csv_integer(&i, DATA);
    if (ANIM -> MAT_LAST_ANIM_DATA_FRAME[j] <= 0)
      inconsistent_data(i);
    i = ignore_wsp_until(i, DATA, true);
    
    // read material tex swap animation data
    int new_tex_num = 0;
    int old_tex_num = -1;
    int keyframe_count = 0;
    // k will count the frame number that is being read from the material animation
    // data. It starts in 1 as the first frame of the animations is frame 1
    for (int k = 1; k <= ANIM -> MAT_LAST_ANIM_DATA_FRAME[j]; k++)
    {
      // get texture integer
      if (DATA[i] != ',')
        new_tex_num = get_csv_integer(&i, DATA);        
      if (new_tex_num < 0)
        inconsistent_data(i);
      
      // write values on ANIM -> MAT_TEX_SWAP_DATA
      //~ printf("\n");
      //~ printf("kf: %d\n", ANIM -> KEYFRAMES[keyframe_count]);   
      while (k != ANIM -> KEYFRAMES[keyframe_count])
      {
        //~ printf("%d ", k);
        ANIM -> MAT_TEX_SWAP_DATA[tex_swap_pos] = old_tex_num;
        tex_swap_pos++;
        k++;
      }
      ANIM -> MAT_TEX_SWAP_DATA[tex_swap_pos] = new_tex_num;
      tex_swap_pos++;
      //~ printf("%d\n", k);
      
      // skip whitespace but not comma or newline
      i = ignore_wsp_until(i, DATA, false);
      
      if (DATA[i] == ',')
        i = ignore_wsp_until(i, DATA, true);
      else if (DATA[i] == '\n')
      {        
        // if newline is reached, store all the non-keyframe
        // frame values on ANIM -> MAT_TEX_SWAP_DATA
        for (int l = 0; l < (ANIM -> MAT_LAST_ANIM_DATA_FRAME[j] - k); l++)
        {
          ANIM -> MAT_TEX_SWAP_DATA[tex_swap_pos] = new_tex_num;
          tex_swap_pos++;
        }
        break;
      }
      else
        inconsistent_data(i);
      
      keyframe_count++;
      old_tex_num = new_tex_num;
      
      // end of line (without a texture number) has been reached
      if (DATA[i] == '\n')
      {
        // store all the non-keyframe frame values on ANIM -> MAT_TEX_SWAP_DATA
        for (int l = 0; l < (ANIM -> MAT_LAST_ANIM_DATA_FRAME[j] - k); l++)
        {
          ANIM -> MAT_TEX_SWAP_DATA[tex_swap_pos] = new_tex_num;
          tex_swap_pos++;
        }
        break;
      }
    }
    
    //~ printf("\n");
    //~ for (int k = 0; k < tex_swap_pos; k++)
      //~ printf("%02u ", ANIM -> MAT_TEX_SWAP_DATA[k]);
    //~ printf("\n");
    
    // get to next line
    i = get_to_nextln_data(i, DATA);
  }
  
  // end of file must've been reached
  if (DATA[i] != 0)
    inconsistent_data(i);
}


///////////////////////////
// PRINT_CSV_BTP_STRUCT() function
// function to print the information pointed to by a built CSV BTP pointer table
// 
// ANIM   (struct CSV_BTP *) --> pointer table for the CSV BTP file
// file_path (byte_t*)           --> CSV BTP file path string
void PRINT_CSV_BTP_STRUCT(byte_t* file_path)
{   
  // temp variable to count
  int temp = 0;
  
  // load file on memory and build its pointer table  
  byte_t* DATA = load_file(file_path);
  struct CSV_BTP ANIM;
  BUILD_CSV_BTP_STRUCT(&ANIM, DATA);
  
  printf("\n### CSV BTP FILE INFORMATION ###\n\n");
  
  printf("Loop mode: %u\n", ANIM.LOOP_MODE);
  printf("Last animation frame: %u\n", ANIM.LAST_ANIM_FRAME);
  printf("Number of materials to animate: %u\n", ANIM.ANIM_MAT_COUNT);
  
  // print animation keyframes
  printf("Animation keyframes (from the material anim data):");
  do {
    printf(" %u", *(ANIM.KEYFRAMES + temp));
    temp++;
  } while (*(ANIM.KEYFRAMES + temp) != 0);
  printf("\n\n");
  
  // print material data
  u4 str_length_sum = 0;
  u4 mat_tex_swap_data_pos = 0;
  for (int i = 0; i < ANIM.ANIM_MAT_COUNT; i++)
  {    
    // print material name
    printf("Material name: ");
    do {
      printf("%c", ANIM.MAT_NAMES[str_length_sum]);
      str_length_sum++;
    } while (ANIM.MAT_NAMES[str_length_sum] != 0);
    printf("\n");
    
    // print material animation length
    printf("Last frame with animation data for material: %u\n", ANIM.MAT_LAST_ANIM_DATA_FRAME[i]);
      
    // print material animation data
    printf("Material texture swap animation (frame 1 to %u):", ANIM.LAST_ANIM_FRAME);
    // reset temp
    temp = 1;
    while (temp <= ANIM.LAST_ANIM_FRAME)
    {
      // if the animation of the material goes further than the last animation data the material has
      // print the last texture swap frame value it had until the last frame of the animation is reached
      printf(" %u", ANIM.MAT_TEX_SWAP_DATA[mat_tex_swap_data_pos]);
      if (temp < ANIM.MAT_LAST_ANIM_DATA_FRAME[i])
        mat_tex_swap_data_pos++;
      temp++;
    }
    mat_tex_swap_data_pos++;
    
    // make an empty line
    printf("\n\n");
  }
  
  // free memory used
  free_memory(DATA);
  free_memory(ANIM.KEYFRAMES);
  free_memory(ANIM.MAT_NAMES);
  free_memory(ANIM.MAT_LAST_ANIM_DATA_FRAME);
  free_memory(ANIM.MAT_TEX_SWAP_DATA);
}


///////////////////////////
// WRITE_BIN_BTP() function
// function to write a binary BTP file from a valid CSV BTP file
//
// anim_file_path   (byte_t*)            --> CSV BTP file path
// ANIM          (struct CSV_BTP *)  --> CSV_BTP data structure pointer
// model_file_path  (byte_t*)            --> BDL file path
void WRITE_BIN_BTP(byte_t* csv_anim_path, byte_t* model_file_path)
{
  // temp variable
  u4 temp = 0;
  
  // - load anim_file and model_file on memory build their structures
  // - get output binary file name from anim_file_path
  // - calculate the size of the BIN BTP and allocate enough space for it on memory
  // - showtime
  
  // load both files in memory
  byte_t* csv_anim_file = load_file(csv_anim_path);
  struct CSV_BTP ANIM;
  BUILD_CSV_BTP_STRUCT(&ANIM, csv_anim_file);
  byte_t* model_file = load_file(model_file_path);
  MODEL_ENDIAN = get_jfile_endianness(model_file_path);
  MODEL_PTR_TB MODEL;
  BUILD_BIN_BMD_BDL_STRUCT(&MODEL, model_file);
  
  // get the new file name
  byte_t* bin_anim_path = allocate_memory(get_str_length(0, csv_anim_path) + 3, 1);
  join_strings(bin_anim_path, 0, csv_anim_path);
  join_strings(bin_anim_path, get_str_length(0, csv_anim_path) - 1, ".btp");
  
  // calculate BIN BTP file size
  u4 file_size = 32; // header
  file_size += 32; // TPT1 header
  for (int i = 0; i < ANIM.ANIM_MAT_COUNT; i++) file_size += 8; // animation table start
  u2 mat_anim_length_sum = 0; // can calculate this now
  for (int i = 0; i < ANIM.ANIM_MAT_COUNT; i++) mat_anim_length_sum += ANIM.MAT_LAST_ANIM_DATA_FRAME[i];
  file_size += 2 * mat_anim_length_sum; // texture swap table
  while (file_size % 4 != 0) file_size++; // padding 1
  file_size += 2 * ANIM.ANIM_MAT_COUNT; // material position table
  while (file_size % 4 != 0) file_size++; // padding 2
  file_size += 4 + 4 * ANIM.ANIM_MAT_COUNT; // material name table start + material name table inf
  for (int i = 0, j = 0; i < ANIM.ANIM_MAT_COUNT; i++) // material names
  {
    j = 0;
    while (ANIM.MAT_NAMES[j] != 0)
      j++;
    j++;
    file_size += j;
  }
  while (file_size % 32 != 0) file_size++; // padding 3
  
  // allocate space for the output file contents in memory
  byte_t* DATA = allocate_memory(file_size, 1);
  u4 i = 0; // variable to track writing position
  
  // start writing shit
  
  // header
  
  // file start
  i += WRITE_DATA_TYPE(4, CH_ARR_ASCII, BTP_FILE_START[1], DATA + i, MODEL_ENDIAN);
  // file id
  i += WRITE_DATA_TYPE(4, CH_ARR_ASCII, BTP_ID[1], DATA + i, MODEL_ENDIAN);
  // file size
  i += WRITE_DATA_TYPE(4, U_INT, &file_size, DATA + i, MODEL_ENDIAN);
  // section count
  i += WRITE_DATA_TYPE(4, CH_ARR_ASCII, &BTP_SECTION_COUNT, DATA + i, MODEL_ENDIAN);
  // unknown 1
  i += WRITE_DATA_TYPE(16, CH_ARR_ASCII, BTP_UNKNOWN_1, DATA + i, MODEL_ENDIAN);
  
  // tpt1 section
  
  // section start
  i += WRITE_DATA_TYPE(4, CH_ARR_ASCII, BTP_SECTIONS_START[0][1], DATA + i, MODEL_ENDIAN);
  // section size
  temp = file_size - 32;
  i += WRITE_DATA_TYPE(4, U_INT, &temp, DATA + i, MODEL_ENDIAN);
  
  // loop mode
  i += WRITE_DATA_TYPE(1, U_INT, &ANIM.LOOP_MODE, DATA + i, MODEL_ENDIAN);
  // unknown 2
  i += WRITE_DATA_TYPE(1, U_INT, &BTP_UNKNOWN_2, DATA + i, MODEL_ENDIAN);
  // last animation frame
  i += WRITE_DATA_TYPE(2, U_INT, &ANIM.LAST_ANIM_FRAME, DATA + i, MODEL_ENDIAN);
  // animated material count
  i += WRITE_DATA_TYPE(2, U_INT, &ANIM.ANIM_MAT_COUNT, DATA + i, MODEL_ENDIAN);
  // material animation length sum (tex swap table length)
  i += WRITE_DATA_TYPE(2, U_INT, &mat_anim_length_sum, DATA + i, MODEL_ENDIAN);
  
  // offsets
  
  // tpt1 section offset
  i += WRITE_DATA_TYPE(4, U_INT, &BTP_TPT1_OFFSET, DATA + i, MODEL_ENDIAN);
  // texture swap table offset
  u4 tex_swap_table_offset = 32 + 8 * ANIM.ANIM_MAT_COUNT;
  i += WRITE_DATA_TYPE(4, U_INT, &tex_swap_table_offset, DATA + i, MODEL_ENDIAN);
  // material position offset
  u4 mat_pos_table_offset = tex_swap_table_offset + 2 * mat_anim_length_sum;
  while (mat_pos_table_offset % 4 != 0) mat_pos_table_offset++;
  i += WRITE_DATA_TYPE(4, U_INT, &mat_pos_table_offset, DATA + i, MODEL_ENDIAN);
  // material name table offset
  u4 mat_name_table_offset = mat_pos_table_offset + 2 * ANIM.ANIM_MAT_COUNT;
  while (mat_name_table_offset % 4 != 0) mat_name_table_offset++;
  i += WRITE_DATA_TYPE(4, U_INT, &mat_name_table_offset, DATA + i, MODEL_ENDIAN);
  
  // animation table start
  mat_anim_length_sum = 0;
  for (int j = 0; j < ANIM.ANIM_MAT_COUNT; j++)
  {
    // last animation data frame for material
    i += WRITE_DATA_TYPE(2, U_INT, &ANIM.MAT_LAST_ANIM_DATA_FRAME[j], DATA + i, MODEL_ENDIAN);
    // material animation data position
    i += WRITE_DATA_TYPE(2, U_INT, &mat_anim_length_sum, DATA + i, MODEL_ENDIAN);
    // unknown 3
    i += WRITE_DATA_TYPE(1, U_INT, &BTP_UNKNOWN_3, DATA + i, MODEL_ENDIAN);
    // unknown 4
    i += WRITE_DATA_TYPE(3, U_INT, &BTP_UNKNOWN_4, DATA + i, MODEL_ENDIAN);
    mat_anim_length_sum += ANIM.MAT_LAST_ANIM_DATA_FRAME[j];
  }
  
  // texture swap table
  for (int j = 0; j < mat_anim_length_sum; j++)
    // tex swap number
    i += WRITE_DATA_TYPE(2, U_INT, &ANIM.MAT_TEX_SWAP_DATA[j], DATA + i, MODEL_ENDIAN);
  
  // padding 1
  i += WRITE_DATA_TYPE(get_padding_size(i, false), PAD, PADDING_STR, DATA + i, MODEL_ENDIAN);
  
  // material pos table
  u4 str_length_sum = 0;
  for (int j = 0; j < ANIM.ANIM_MAT_COUNT; j++)
  {
    u4 mat_str_length = 0;
    while (ANIM.MAT_NAMES[str_length_sum + mat_str_length] != 0)
      mat_str_length++;
    mat_str_length++;
    
    byte_t* mat_name = allocate_memory(mat_str_length, 1);
    mat_str_length = 0;
    while (ANIM.MAT_NAMES[str_length_sum] != 0)
      mat_name[mat_str_length++] = ANIM.MAT_NAMES[str_length_sum++];
    str_length_sum++;
    mat_str_length++;
    
    str_length_sum -= mat_str_length;
    for (u2 k = 0;
         k < GET_DATA_TYPE(2, U_INT, model_file + MODEL.MAT_COUNT, NULL, MODEL_ENDIAN);
         k++)
    {
      u4 mat_str_pos_bdl = (MODEL.MAT_NAME_TABLE) +
        (u2) GET_DATA_TYPE(2, U_INT, model_file + MODEL.MAT_NAME_INF + 2 + (4 * k), NULL, MODEL_ENDIAN);      
      if (compare_strings(ANIM.MAT_NAMES + str_length_sum, model_file + mat_str_pos_bdl, 0))
      {
        // mat pos number
        i += WRITE_DATA_TYPE(2, U_INT, &k, DATA + i, MODEL_ENDIAN);
        break;
      }
    }
    str_length_sum += mat_str_length;
    free_memory(mat_name);
  }
  
  // padding 2
  i += WRITE_DATA_TYPE(get_padding_size(i, false), PAD, PADDING_STR, DATA + i, MODEL_ENDIAN);
  
  // material name table start (material count)
  i += WRITE_DATA_TYPE(2, U_INT, &ANIM.ANIM_MAT_COUNT, DATA + i, MODEL_ENDIAN);
  // unknown 5
  i += WRITE_DATA_TYPE(2, U_INT, &BTP_UNKNOWN_5, DATA + i, MODEL_ENDIAN);
  
  // material name inf
  str_length_sum = 0;
  for (int j = 0; j < ANIM.ANIM_MAT_COUNT; j++)
  {
    u4 mat_str_length = 0;
    while (ANIM.MAT_NAMES[str_length_sum + mat_str_length] != 0)
      mat_str_length++;
    mat_str_length++;
    
    u2 mat_str_weight = get_str_weight(str_length_sum, ANIM.MAT_NAMES);
    u2 mat_str_pos_btp = 4 + (4 * ANIM.ANIM_MAT_COUNT) + str_length_sum;
    // material string weight
    i += WRITE_DATA_TYPE(2, U_INT, &mat_str_weight, DATA + i, MODEL_ENDIAN);
    // material string position
    i += WRITE_DATA_TYPE(2, U_INT, &mat_str_pos_btp, DATA + i, MODEL_ENDIAN);    
    str_length_sum += mat_str_length;
  }
  
  // material name list
  for (int j = 0; j < str_length_sum; j++)
    i += WRITE_DATA_TYPE(1, U_INT, ANIM.MAT_NAMES + j, DATA + i, MODEL_ENDIAN);
    
  // padding 3
  i += WRITE_DATA_TYPE(get_padding_size(i, true), PAD, PADDING_STR, DATA + i, MODEL_ENDIAN);
  
  //~ // print BIN BTP file contents in memory
  //~ printf("\nBinary BTP file contents:\n");
  //~ for (int i = 0; i < file_size; i++)
  //~ {
    //~ printf("%02X ", (unsigned char) * (DATA + i));
    //~ if ((i + 1) % 16 == 0 && i != 0)
      //~ printf("\n");
  //~ }  
  
  // dump memory loaded bin BTP into a file  
  // create the sacred BIN BTP file
  create_file(bin_anim_path, file_size, DATA);  
  // CSV BTP file has been converted to its BIN equivalent!
  printf("\nFile %s has been created.\n", bin_anim_path);
  
  // free memory used
  free_memory(csv_anim_file);
  free_memory(model_file);
  free_memory(bin_anim_path);
  free_memory(DATA);
  free_memory(ANIM.KEYFRAMES);
  free_memory(ANIM.MAT_NAMES);
  free_memory(ANIM.MAT_LAST_ANIM_DATA_FRAME);
  free_memory(ANIM.MAT_TEX_SWAP_DATA);
}

// end of header
#endif
